/*
 * $Id: tio.c,v 1.13 2006-01-01 13:50:24 bacon Exp $
 */

#include <xp/bas/tio.h>
#include <xp/bas/memory.h>
#include <xp/bas/assert.h>
#include <xp/bas/errno.h>

xp_tio_t* xp_tio_open (xp_tio_t* tio)
{
	if (tio == XP_NULL) {
		tio = (xp_tio_t*) xp_malloc (xp_sizeof(xp_tio_t));
		if (tio == XP_NULL) return XP_NULL;
		tio->__dynamic = xp_true;
	}
	else tio->__dynamic = xp_false;

	tio->input_func = XP_NULL;
	tio->input_arg = XP_NULL;
	tio->output_func = XP_NULL;
	tio->output_arg = XP_NULL;

        tio->input_status = 0;
        tio->inbuf_curp = 0;
        tio->inbuf_len = 0;
        tio->outbuf_len = 0;

	tio->errnum = XP_TIO_ENOERR;

	return tio;
}

int xp_tio_close (xp_tio_t* tio)
{
	xp_tio_flush (tio); /* don't care about the result */
	if (xp_tio_detinp(tio) == -1) return -1;
	if (xp_tio_detoutp(tio) == -1) return -1;
	if (tio->__dynamic) xp_free (tio);
	return 0;
}

int xp_tio_geterrnum (xp_tio_t* tio)
{
	return tio->errnum;
}

const xp_char_t* xp_tio_geterrstr (xp_tio_t* tio)
{
	static const xp_char_t* __errstr[] =
	{
		XP_TEXT("no error"),
		XP_TEXT("out of memory"),
		XP_TEXT("no more space"),
		XP_TEXT("illegal utf-8 sequence"),
		XP_TEXT("no input function attached"),
		XP_TEXT("input function returned an error"),
		XP_TEXT("input function failed to open"),
		XP_TEXT("input function failed to closed"),
		XP_TEXT("no output function attached"),
		XP_TEXT("output function returned an error"),
		XP_TEXT("output function failed to open"),
		XP_TEXT("output function failed to closed"),
		XP_TEXT("unknown error")
	};

	return __errstr[
		(tio->errnum < 0 || tio->errnum >= xp_countof(__errstr))? 
		xp_countof(__errstr) - 1: tio->errnum];
}

int xp_tio_attinp (xp_tio_t* tio, xp_tio_io_t input, void* arg)
{
	if (xp_tio_detinp(tio) == -1) return -1;

	xp_assert (tio->input_func == XP_NULL);

	if (input(XP_TIO_IO_OPEN, arg, XP_NULL, 0) == -1) {
		tio->errnum = XP_TIO_EINPOP;
		return -1;
	}

	tio->input_func = input;
	tio->input_arg = arg;

        tio->input_status = 0;
        tio->inbuf_curp = 0;
        tio->inbuf_len = 0;

	return 0;
}

int xp_tio_detinp (xp_tio_t* tio)
{
	if (tio->input_func != XP_NULL) {
		if (tio->input_func (
			XP_TIO_IO_CLOSE, tio->input_arg, XP_NULL, 0) == -1) {
			tio->errnum = XP_TIO_EINPCL;
			return -1;
		}

		tio->input_func = XP_NULL;
		tio->input_arg = XP_NULL;
	}
		
	return 0;
}

int xp_tio_attoutp (xp_tio_t* tio, xp_tio_io_t output, void* arg)
{
	if (xp_tio_detoutp(tio) == -1) return -1;

	xp_assert (tio->output_func == XP_NULL);

	if (output(XP_TIO_IO_OPEN, arg, XP_NULL, 0) == -1) {
		tio->errnum = XP_TIO_EOUTOP;
		return -1;
	}

	tio->output_func = output;
	tio->output_arg = arg;
        tio->outbuf_len = 0;

	return 0;
}

int xp_tio_detoutp (xp_tio_t* tio)
{
	if (tio->output_func != XP_NULL) {
		xp_tio_flush (tio); /* don't care about the result */

		if (tio->output_func (
			XP_TIO_IO_CLOSE, tio->output_arg, XP_NULL, 0) == -1) {
			tio->errnum = XP_TIO_EOUTCL;
			return -1;
		}

		tio->output_func = XP_NULL;
		tio->output_arg = XP_NULL;
	}
		
	return 0;
}

xp_ssize_t xp_tio_flush (xp_tio_t* tio)
{
	xp_size_t left, count;

	if (tio->output_func == XP_NULL) {
		tio->errnum = XP_TIO_ENOUTF;
		xp_seterrno (XP_EINVAL);
		return (xp_ssize_t)-1;
	}

	left = tio->outbuf_len;
	while (left > 0) {
		xp_ssize_t n;

		n = tio->output_func (
			XP_TIO_IO_DATA, tio->output_arg, tio->outbuf, left);
		if (n == -1) {
			tio->outbuf_len = left;
			tio->errnum = XP_TIO_EOUTPT;
			return -1;
		}
		if (n == 0) break;
	
		left -= n;
		xp_memcpy (tio->outbuf, &tio->inbuf[n], left);
	}

	count = tio->outbuf_len - left;
	tio->outbuf_len = left;

	return (xp_ssize_t)count;
}

void xp_tio_purge (xp_tio_t* tio)
{
        tio->input_status = 0;
        tio->inbuf_curp = 0;
        tio->inbuf_len = 0;
        tio->outbuf_len = 0;
	tio->errnum = XP_TIO_ENOERR;
}
